/*
 * Copyright (C) 2010 M. Hautle.
 * 
 * This file is part of SimpleEL.
 * 
 * SimpleEL is free software: you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free
 * Software Foundation, either version 3 of the License, or (at your option) any later version.
 * 
 * SimpleEL is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
 * PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details.
 * 
 * You should have received a copy of the GNU Lesser General Public License along with SimpleEL. If not, see <http://www.gnu.org/licenses/>.
 */
package ch.simpleel.internal.reflection;

import java.beans.*;
import java.lang.reflect.*;
import java.util.*;
import ch.msoftch.internal.*;
import ch.simpleel.util.*;

/**
 * Utility class for reflection stuff.
 * 
 * @author hautle
 */
public final class ReflectionHelper {
    /** Cache of used properties. The key is built from the specification of the desired property. */
    private static final ICache<PropertyKey, PropertyDescriptor> PROPERTIES = CacheFactory.createCache(PropertyKey.class, PropertyDescriptor.class);

    /** Cached names of the properties from a class. */
    private static final ICache<Class, String[]> PROPERTY_NAMES = CacheFactory.createCache(Class.class, String[].class);

    /** Cached names of the mutable properties from a class. */
    private static final ICache<Class, String[]> MUTABLE_PROPERTY_NAMES = CacheFactory.createCache(Class.class, String[].class);

    /**
     * Hidden constructor.
     */
    private ReflectionHelper() {
    }

    /**
     * Returns the descriptor of a given property or null if it does not exists.
     * 
     * @param type The type
     * @param property The property name
     * @return The descriptor of the given property or null
     * @throws TechnicalException If something goes wrong
     */
    public static PropertyDescriptor getDescriptor(Class<?> type, String property) throws TechnicalException {
        final PropertyKey key = new PropertyKey(type, property);
        final PropertyDescriptor desc = PROPERTIES.get(key);
        if (desc != null)
            return desc;
        try {
            for (PropertyDescriptor p : Introspector.getBeanInfo(type).getPropertyDescriptors()) {
                if (property.equals(p.getName())) {
                    PROPERTIES.put(key, p);
                    return p;
                }
            }
        } catch (IntrospectionException e) {
            throw new TechnicalException("Error during property lookup of " + property + " in " + type.getName(), e);
        }
        return null;
    }

    /**
     * Returns the getter of a given property or null if it does not exists.
     * 
     * @param type The type
     * @param property The property name
     * @return The getter of the given property or null
     * @throws TechnicalException If something goes wrong
     */
    public static Method getGetter(Class<?> type, String property) {
        final PropertyDescriptor desc = getDescriptor(type, property);
        if (desc == null)
            return null;
        return desc.getReadMethod();
    }

    /**
     * Returns the setter of a given property or null if it does not exists.
     * 
     * @param type The type
     * @param property The property name
     * @return The setter of the given property or null
     * @throws TechnicalException If something goes wrong
     */
    public static Method getSetter(Class<?> type, String property) {
        final PropertyDescriptor desc = getDescriptor(type, property);
        if (desc == null)
            return null;
        return desc.getWriteMethod();
    }

    /**
     * Creates a map holding the {@link PropertyDescriptor}s (key=name) of the given class.
     * 
     * @param type The type
     * @return A map holding the properties
     * @throws TechnicalException If something goes wrong
     */
    public static Map<String, PropertyDescriptor> getProperties(Class<?> type) throws TechnicalException {
        try {
            final Map<String, PropertyDescriptor> props = new HashMap<String, PropertyDescriptor>();
            for (PropertyDescriptor p : Introspector.getBeanInfo(type).getPropertyDescriptors())
                props.put(p.getName(), p);
            return props;
        } catch (IntrospectionException e) {
            throw new TechnicalException("Error while getting property list from " + type.getName(), e);
        }
    }

    /**
     * Returns the names of a all properties from the given class.
     * 
     * @param c The class
     * @return The property names ordered by their natural ordering
     */
    public static String[] getPropertyNames(Class<?> c) {
        String[] names = PROPERTY_NAMES.get(c);
        if (names != null)
            return names;
        try {
            final PropertyDescriptor[] desc = Introspector.getBeanInfo(c).getPropertyDescriptors();
            int i = 0;
            names = new String[desc.length];
            for (PropertyDescriptor p : desc)
                if (!(p instanceof IndexedPropertyDescriptor))
                    names[i++] = p.getName();
            // trim the array size if necessary
            if (i < desc.length)
                names = Arrays.copyOfRange(names, 0, i);
            Arrays.sort(names);
            PROPERTY_NAMES.put(c, names);
            return names;
        } catch (IntrospectionException e) {
            throw new TechnicalException("Error while getting property list from " + c.getName(), e);
        }
    }

    /**
     * Returns the names of a all mutable properties from the given class.<br>
     * Mutable properties must have a getter and a setter method.
     * 
     * @param c The class
     * @return The property names ordered by their natural ordering
     */
    public static String[] getMutablePropertyNames(Class<?> c) {
        String[] names = MUTABLE_PROPERTY_NAMES.get(c);
        if (names != null)
            return names;
        try {
            final PropertyDescriptor[] desc = Introspector.getBeanInfo(c).getPropertyDescriptors();
            int i = 0;
            names = new String[desc.length];
            for (PropertyDescriptor p : desc)
                if (!(p instanceof IndexedPropertyDescriptor) && p.getWriteMethod() != null && p.getReadMethod() != null)
                    names[i++] = p.getName();
            // trim the array size if necessary
            if (i < desc.length)
                names = Arrays.copyOfRange(names, 0, i);
            Arrays.sort(names);
            MUTABLE_PROPERTY_NAMES.put(c, names);
            return names;
        } catch (IntrospectionException e) {
            throw new TechnicalException("Error while getting property list from " + c.getName(), e);
        }
    }

    /**
     * Clears the cache.
     */
    public static void clearCache() {
        PROPERTIES.clear();
        MUTABLE_PROPERTY_NAMES.clear();
        PROPERTY_NAMES.clear();
    }
}
